﻿using System;
using System.Diagnostics;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.ServiceModel.Web;
using Microsoft.Samples.WindowsAzure.ServiceManagement;

namespace InstanceControllerWorker.ManageCommands
{
	/// <summary>
	/// Parts of this code are in part provided through Microsoft's MSDN Code Gallery project samples. http://archive.msdn.microsoft.com/windowsazuresamples
	/// </summary>
	public partial class CSManageCommand
	{
		public const int PollTimeoutInSeconds = 180;	//180*10seconds = 30 minutes, how long we wait for an Azure command executes before quitting.

		#region from settings: configuration variables
		private static string _deploymentSlot = "Staging";
		public static string DeploymentSlot
		{
			get { return _deploymentSlot; }
			set { _deploymentSlot = value; }
		}
		public static string DeploymentId { get; set; }
		public static string SubscriptionId { get; set; }
		public static string HostedServiceName { get; set; }
		public static string RoleName { get; set; }
		public static string CertificateThumbprint { get; set; }
		public static int MaxInstances { get; set; }
		public static int MinInstances { get; set; }
		#endregion

		/// <summary>
		/// Gets or sets the certificate. This static property is set during the ValidateCertificate method call.
		/// </summary>
		/// <value>The certificate.</value>
		public static X509Certificate2 Certificate { get; set; }

		/// <summary>
		/// Validates the local certificate against the server certificate and sets the X509 Certificate 
		/// static property by using the certificate thumbprint specified in the settings. You must have
		/// a certificate defined in the ServiceDefinition.csdef for this purpose.
		/// </summary>
		/// <returns></returns>
		/// <remarks>This method set the static Certificate property used throughout the class when making
		/// exchanges with the server.</remarks>
		public static bool ValidateCertificate()
		{
			if (String.IsNullOrEmpty(CertificateThumbprint))
			{
				Trace.WriteLine("CertificateThumbprint cannot be found. Please check the config file. ", "Debug");
				WorkerRole.LogProcess("ValidateCertificate", "CertificateThumbprint cannot be found. Please check the config file.");
				return false;
			}

			//X509Store certificateStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);	//Certificate is in your Personal Certificate store on your computer.
			X509Store certificateStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);	//Certificate is installed locally such as in the Azure application certificate store.
			certificateStore.Open(OpenFlags.ReadOnly);
			X509Certificate2Collection certs = certificateStore.Certificates.Find(X509FindType.FindByThumbprint, CertificateThumbprint, false);
			if (certs.Count != 1)
			{
				//When deploying this application in Azure you must have the same certificate for management in BOTH the Management Certificates (.cer type) and in
				//the application certificates (.pfx type) both must contain the private key. (see the discussion at the end of the WorkerRole.cs file)
				Trace.WriteLine("Client certificate cannot be found. Please check the config file. ", "Debug");
				WorkerRole.LogProcess("ValidateCertificate", "Client certificate cannot be found. You must have a certificate defined in the ServiceDefinition.csdef file.");
				return false;
			}
			Certificate = certs[0];		//sets the static property.
			return true;
		}

		/// <summary>
		/// Runs command instance.
		/// </summary>
		public void Run()
		{
			var serviceManagement = ServiceManagementHelper.CreateServiceManagementChannel("WindowsAzureEndPoint", Certificate);
			Trace.WriteLine("Using certificate: " + Certificate.SubjectName.Name);
			WorkerRole.LogProcess("CSManagerCommand:Run", "Using certificate: " + Certificate.SubjectName.Name);

			try
			{
				string trackingId = null;
				HttpStatusCode? statusCode = null;
				string statusDescription = null;

				using (OperationContextScope scope = new OperationContextScope((IContextChannel)serviceManagement))
				{
					try
					{
						WorkerRole.LogProcess("CSManagerCommand:Run:PerformOperation", "Calling the method");
						PerformOperation(serviceManagement);
						if (WebOperationContext.Current.IncomingResponse != null)
						{
							trackingId = WebOperationContext.Current.IncomingResponse.Headers[Constants.OperationTrackingIdHeader];
							statusCode = WebOperationContext.Current.IncomingResponse.StatusCode;
							statusDescription = WebOperationContext.Current.IncomingResponse.StatusDescription;
							Trace.WriteLine(String.Format("Operation ID: {0}", trackingId));
							WorkerRole.LogProcess("CSManagerCommand:Run", String.Format("Operation ID: {0}", trackingId));
						}
					}
					catch (CommunicationException ce)
					{
						ServiceManagementError error = null;
						HttpStatusCode httpStatusCode;
						string operationId;
						ServiceManagementHelper.TryGetExceptionDetails(ce, out error, out httpStatusCode, out operationId);
						if (error == null)
						{
							Trace.WriteLine(ce.Message);
							WorkerRole.LogProcess("CSManagerCommand:Run:TryGetExceptionDetails", ce.Message);
						}
						else
						{
							Trace.WriteLine(String.Format("HTTP Status Code: {0}", httpStatusCode));
							Trace.WriteLine(String.Format("Error Message: {0}", error.Message));
							Trace.WriteLine(String.Format("Operation Id: {0}", operationId));
							WorkerRole.LogProcess("CSManagerCommand:Run:CommunicationException", String.Format("HTTP Status Code: {0}", httpStatusCode));
							WorkerRole.LogProcess("CSManagerCommand:Run:CommunicationException", String.Format("Error Message: {0}", error.Message));
							WorkerRole.LogProcess("CSManagerCommand:Run:CommunicationException", String.Format("Operation Id: {0}", operationId));
						}
					}
					finally
					{
						if (statusCode != null)
						{
							Trace.WriteLine(String.Format("HTTP Status Code: {0}", statusCode));
							Trace.WriteLine(String.Format("StatusDescription: {0}", statusDescription));
							WorkerRole.LogProcess("CSManagerCommand:Run:finally", String.Format("HTTP Status Code: {0}", statusCode));
							WorkerRole.LogProcess("CSManagerCommand:Run:finally", String.Format("StatusDescription: {0}", statusDescription));
						}
					}
				}
				if (trackingId != null && statusCode != null && statusCode == HttpStatusCode.Accepted)
				{
					Trace.WriteLine("Waiting for async operation to complete:");
					WorkerRole.LogProcess("CSManagerCommand:Run:WaitForAsyncOperation", "Waiting for async operation to complete:");
					WaitForAsyncOperation(serviceManagement, trackingId);
				}
			}
			catch (TimeoutException ex)
			{
				Trace.WriteLine("There was an error processing this command.");
				WorkerRole.LogProcess("CSManagerCommand:Run:TimeoutException", ex.Message);
			}
			catch(Exception ex)
			{
				WorkerRole.LogProcess("CSManagerCommand:Run:Exception", ex.Message);
			}
		}

		/// <summary>
		/// Waits for async operation.
		/// Commands issued to Azure serivces are asynchronous. We must poll Azure for status per
		/// the MSDN Windows Azure Service API reference:
		/// http://msdn.microsoft.com/en-us/library/ee460799.aspx
		/// </summary>
		/// <param name="service">The service.</param>
		/// <param name="trackingId">The tracking id.</param>
		private static void WaitForAsyncOperation(IServiceManagement service, string trackingId)
		{
			Operation tracking;
			var count = 0;
			WorkerRole.LogProcess("WaitForAsyncOperation", "Begin:" + trackingId);

			try
			{

				do
				{
					// Checking to see if the operation at the specified ID has completed by retrieving the status.
					// http://msdn.microsoft.com/en-us/library/ee460783.aspx
					tracking = service.GetOperationStatus(SubscriptionId, trackingId);
					System.Threading.Thread.Sleep(10000); // wait on the operation to complete at Azure in 10 second intervals.

					count++;
					WorkerRole.LogProcess("WaitForAsyncOperation", "Pending... " + count); 
					if (count <= PollTimeoutInSeconds) continue; // keep waiting until we have data or until we time out

					Trace.WriteLine(String.Format("Giving up after {0} seconds. Call GetResult manually to get the status",
					                              PollTimeoutInSeconds));
					WorkerRole.LogProcess("WaitForAsyncOperation",
					                      String.Format("Giving up after {0} seconds. Call GetResult manually to get the status",
					                                    PollTimeoutInSeconds));
					break;
				} while (tracking.Status != OperationState.Failed && tracking.Status != OperationState.Succeeded);
					// check if we have data...

				//Todo: logging of operational data
				if (tracking.Status != OperationState.InProgress)
				{
					Trace.WriteLine("Done");
					WorkerRole.LogProcess("WaitForAsyncOperation:tracking.Status", "Done");
				}

				Trace.WriteLine("Operation Status=" + tracking.Status);
				WorkerRole.LogProcess("CSManagerCommand:Run", "Operation Status = " + tracking.Status);

				if (tracking.Status == OperationState.Failed)
				{
					Trace.WriteLine(String.Format("Error Message: {0}", tracking.Error.Message));
					WorkerRole.LogProcess("WaitForAsyncOperation:tracking.Status:OperationState.Failed",
					                      String.Format("Error Message: {0}", tracking.Error.Message));
				}
			}
			catch (Exception ex)
			{
				WorkerRole.LogProcess("WaitForAsyncOperation:tracking.Status:OperationState.Failed", ex.Message);
			}
		}

		/// <summary>
		/// Performs the operation. You must override this method in your specific command.
		/// </summary>
		/// <param name="channel">The channel.</param>
		protected virtual void PerformOperation(IServiceManagement channel) { }
        

	}

	public enum LogLevels
	{
		Information,
		Status,
		Error,
		Debug
	}

}
